#include "sal.h"
#include <linux/jiffies.h>

#ifdef SAL_DEBUG_MUTEX
struct sal_mutex
{
    struct semaphore sem;
    uint32 is_used;
    pid_t tid;
    char lk_hold_location[SAL_LOC_NAME_MAX];
    char ulk_hold_location[SAL_LOC_NAME_MAX];
};

sal_task_mutex_t g_task_mutex;
static sal_mutex_t* mutex_mutex;

char location1[SAL_LOC_NAME_MAX] = "mem_mgr_add_to_mtype_table";
char location2[SAL_LOC_NAME_MAX] = "mem_mgr_remove_from_mtype_table";
#else
struct sal_mutex
{
    struct semaphore sem;
};
#endif

struct sal_sem
{
    struct semaphore sem;
};

sal_err_t
ctc_sal_mutex_create(sal_mutex_t** pmutex)
{
    sal_mutex_t* mutex;

    SAL_MALLOC(mutex, sal_mutex_t*, sizeof(sal_mutex_t));
    if (!mutex)
    {
        return ENOMEM;
    }

    sema_init(&mutex->sem, 1);

#ifdef SAL_DEBUG_MUTEX
    mutex->is_used = 0;
    mutex->tid = 0;
    sal_memset(mutex->lk_hold_location, 0, SAL_LOC_NAME_MAX);
    sal_memset(mutex->ulk_hold_location, 0, SAL_LOC_NAME_MAX);
#endif

    *pmutex = mutex;
    return 0;
}

void
ctc_sal_mutex_destroy(sal_mutex_t* mutex)
{
    if (!mutex)
    {
        return ;
    }
    while (mutex->sem.count <= 0)
    {
        sal_task_sleep(1);
    }
    SAL_FREE(mutex);
}

#ifdef SAL_DEBUG_MUTEX
void
_ctc_sal_mutex_free_db()
{
    uint8 i = 0;
    uint8 j = g_task_mutex.tid_num -1;
    sal_time_t current_time = 0;
    sal_time_t old_time = 0;
    sal_time_t old_time2 = 0;

    for(i = 0; i<= j; i++)
    {
        sal_time(&current_time);
        old_time = g_task_mutex.info[i]->time;
        if((current_time - old_time) < 300) /*timer 5min*/
        {
            continue;
        }
        else
        {
            if(i < j)
            {
                /*find active task from last, and move to current loaction*/
                for ( ; j >= i; j--)
                {
                    old_time2 = g_task_mutex.info[j]->time;
                    if((current_time - old_time2) < 300) /*timer 5min*/
                    {
                        SAL_FREE(g_task_mutex.info[i]);
                        g_task_mutex.info[i] = g_task_mutex.info[j];
                        g_task_mutex.info[j] = NULL;
                        g_task_mutex.tid_num--;
                        j = g_task_mutex.tid_num - 1;
                        break;
                    }
                    else
                    {
                        SAL_FREE(g_task_mutex.info[j]);
                        g_task_mutex.tid_num--;
                        continue;
                    }
                }
            }
            else
            {
                SAL_FREE(g_task_mutex.info[i]);
                g_task_mutex.tid_num--;
                continue;
            }
        }
    }
}

void
ctc_sal_mutex_lock(sal_mutex_t* mutex, const char *take_loc, int line)
{
    int ret = 0;
    char lineNum[8] = {'\0'};
    pid_t tid = 0;
    uint32 i = 0;
    sal_task_mutex_info_t* tmp_info = NULL;
    sal_task_mutex_info_t* conflict_info = NULL;
    char req_location[SAL_LOC_NAME_MAX] = {0};

    if (!mutex)
    {
        return ;
    }

    tid = ctc_sal_gettid();
    sal_sprintf(lineNum, ":%d", line);
    sal_strncpy(req_location, take_loc, SAL_LOC_NAME_MAX - 7);
    sal_strcat(req_location, lineNum);

    if(mutex_mutex)
    {
        /*for multi task*/
        ret = down_interruptible(&mutex_mutex->sem);
    }

    if(!(!sal_strcmp(take_loc,location1) || !sal_strcmp(take_loc,location2)))
    {
        if(g_task_mutex.tid_num > SAL_MUTEX_UPDATE_THRD)
        {
            /*remove not active task db*/
            _ctc_sal_mutex_free_db();
        }

        for(i = 0; i< g_task_mutex.tid_num; i++)
        {
            if(g_task_mutex.info[i]->tid == tid)
            {
                tmp_info = g_task_mutex.info[i];
                tmp_info->req_lk = mutex;
                sal_strncpy(tmp_info->lk_req_location, req_location, SAL_LOC_NAME_MAX);
                break;
            }
        }

        /*task Id first lock, need create db*/
        if(!tmp_info && g_task_mutex.init)
        {
            SAL_MALLOC(tmp_info, sal_task_mutex_info_t*, sizeof(sal_task_mutex_info_t));
            if (!tmp_info)
            {
                CTC_SAL_DEBUG_OUT("[MUTEX] No Memory!!!\n");
                if(mutex_mutex)
                {
                    /*for multi task*/
                    up(&mutex_mutex->sem);
                }
                return;
            }
            sal_memset(tmp_info,0,sizeof(sal_task_mutex_info_t));
            sal_strncpy(tmp_info->lk_req_location, req_location, SAL_LOC_NAME_MAX);
            tmp_info->tid = tid;
            tmp_info->req_lk = mutex;
            /*update db*/
            g_task_mutex.info[g_task_mutex.tid_num] = tmp_info;
            g_task_mutex.tid_num +=1;
        }

        /*check Self-Locking or Interlocked*/
        if(tmp_info && mutex->is_used)
        {
            if(tid == mutex->tid)
            {
                CTC_SAL_DEBUG_OUT("\nExistential Self-Locking!!!\n");
                CTC_SAL_DEBUG_OUT("%-10s, %-64s, %-64s\n","Task Id", "hold Lock Location", "Request Lock Location");
                CTC_SAL_DEBUG_OUT("%-10d, %-64s, %-64s\n",mutex->tid, mutex->lk_hold_location, tmp_info->lk_req_location);
                tmp_info->req_lk = NULL;
                if(mutex_mutex)
                {
                    /*for multi task*/
                    up(&mutex_mutex->sem);
                }
                return;
            }

            for(i = 0; i< g_task_mutex.tid_num; i++)
            {
                if(g_task_mutex.info[i]->tid != mutex->tid)
                {
                    continue;
                }

                conflict_info = g_task_mutex.info[i];

                if(conflict_info->req_lk && conflict_info->req_lk->is_used && conflict_info->req_lk->tid == tid)
                {
                    CTC_SAL_DEBUG_OUT("\nExistential Interlocked!!!\n");
                    CTC_SAL_DEBUG_OUT("%-10s, %-64s, %-64s\n","Task Id", "hold Lock Location", "Request Lock Location");
                    CTC_SAL_DEBUG_OUT("%-10d, %-64s, %-64s\n",mutex->tid, mutex->lk_hold_location, conflict_info->lk_req_location);
                    CTC_SAL_DEBUG_OUT("%-10d, %-64s, %-64s\n",tid, conflict_info->req_lk->lk_hold_location, tmp_info->lk_req_location);
                    tmp_info->req_lk = NULL;
                    if(mutex_mutex)
                    {
                        /*for multi task*/
                        up(&mutex_mutex->sem);
                    }
                    return;
                }
                break;
            }
        }
    }

    if(mutex_mutex)
    {
        /*for multi task*/
        up(&mutex_mutex->sem);
    }

    ret = down_interruptible(&mutex->sem);

    if(mutex_mutex)
    {
        /*for multi task*/
        ret = down_interruptible(&mutex_mutex->sem);
    }

    mutex->tid = tid;
    mutex->is_used = 1;

    sal_strncpy(mutex->lk_hold_location, req_location, SAL_LOC_NAME_MAX);

    if(tmp_info)
    {
        tmp_info->req_lk = NULL;
        tmp_info->hold_lk[tmp_info->hold_lk_cnt] = mutex;
        tmp_info->hold_lk_cnt++;
        sal_time(&(tmp_info->time));
    }

    if(mutex_mutex)
    {
        /*for multi task*/
        up(&mutex_mutex->sem);
    }

    return;
}

void
ctc_sal_mutex_unlock(sal_mutex_t* mutex, const char *give_loc, int line)
{
    int ret = 0;
    pid_t tid = 0;
    sal_task_mutex_info_t* tmp_mutex_info = NULL;
    sal_mutex_t* tmp_mutex = NULL;
    char lineNum[8] = {'\0'};
    char req_location[SAL_LOC_NAME_MAX] = {0};
    uint32 tmp_cnt = 0;
    uint32 i = 0;
    uint32 j = 0;

    if (!mutex)
    {
        return ;
    }

    tid = ctc_sal_gettid();
    sal_sprintf(lineNum, ":%d", line);
    sal_strncpy(req_location, give_loc, SAL_LOC_NAME_MAX - 7);
    sal_strcat(req_location, lineNum);

    if(mutex_mutex)
    {
        /*for multi task*/
        ret = down_interruptible(&mutex_mutex->sem);
    }

    if(!(!sal_strcmp(give_loc,location1) || !sal_strcmp(give_loc,location2)))
    {
        if(!mutex->is_used || mutex->tid != tid)
        {
            CTC_SAL_DEBUG_OUT("\nERROR Happen : %s\n", ((!mutex->is_used) ? "Have Been Unlocked!!!" : "NOT Expect Task Id!!!"));
            CTC_SAL_DEBUG_OUT("Current Task Id : %-10d\n", tid);
            CTC_SAL_DEBUG_OUT("Current Request UnLock Location : %-64s\n", req_location);
            if(!mutex->is_used)
            {
                CTC_SAL_DEBUG_OUT("Last UnLock Location            : %-64s\n", mutex->ulk_hold_location);
                CTC_SAL_DEBUG_OUT("Last Lock Location              : %-64s\n", mutex->lk_hold_location);
            }
            else
            {
                CTC_SAL_DEBUG_OUT("\nHold Task Id :%-10d\n", mutex->tid);
                CTC_SAL_DEBUG_OUT("Hold Lock Location              : %-64s\n", mutex->lk_hold_location);
            }
            if(mutex_mutex)
            {
                /*for multi task*/
                up(&mutex_mutex->sem);
            }
            return;
        }

        for(i = 0; i< g_task_mutex.tid_num; i++)
        {
            if(g_task_mutex.info[i]->tid != tid)
            {
                continue;
            }

            tmp_mutex_info = g_task_mutex.info[i];
            tmp_cnt = tmp_mutex_info->hold_lk_cnt - 1;

            sal_strncpy(tmp_mutex_info->ulk_req_location , req_location, SAL_LOC_NAME_MAX);

            for(j = 0; j < tmp_mutex_info->hold_lk_cnt; j++)
            {
                if(mutex != tmp_mutex_info->hold_lk[j])
                {
                    continue;
                }

                tmp_mutex = tmp_mutex_info->hold_lk[tmp_cnt];

                if(j != tmp_cnt)
                {
                    CTC_SAL_DEBUG_OUT("\nERROR Happen : Not latest Lock!!!\n");
                    CTC_SAL_DEBUG_OUT("Task %d should UnLock Location : %-64s,  Current UnLock Location : %-64s\n",tid, tmp_mutex->lk_hold_location, req_location);
                    if(mutex_mutex)
                    {
                        /*for multi task*/
                        up(&mutex_mutex->sem);
                    }
                    return;
                }
                break;
            }
            break;
        }

    }

    sal_strncpy(mutex->ulk_hold_location, req_location, SAL_LOC_NAME_MAX);

    if(tmp_mutex_info)
    {
        tmp_mutex_info->hold_lk[tmp_cnt] = NULL;
        tmp_mutex_info->hold_lk_cnt = tmp_cnt;
    }

    mutex->is_used = 0;
    mutex->tid = 0;

    if(mutex_mutex)
    {
        /*for multi task*/
        up(&mutex_mutex->sem);
    }

    up(&mutex->sem);

    return;
}

void
ctc_sal_mutex_dump(pid_t tid)
{
    sal_task_mutex_info_t* tmp_mutex_info = NULL;
    int ret = 0;
    uint8 all = !tid;
    uint8 i = 0;
    uint8 j = 0;

    if(mutex_mutex)
    {
        /*for multi task*/
        ret = down_interruptible(&mutex_mutex->sem);
    }

    for(i = 0; i< g_task_mutex.tid_num; i++)
    {
        tmp_mutex_info = g_task_mutex.info[i];
        if(all || tid == tmp_mutex_info->tid)
        {
            CTC_SAL_DEBUG_OUT("\n===========================================================================\n");
            CTC_SAL_DEBUG_OUT("Task Id : %-10d\n", tmp_mutex_info->tid);
            CTC_SAL_DEBUG_OUT("Request Lock Location : %-64s\n", (tmp_mutex_info->req_lk ? tmp_mutex_info->lk_req_location : "-"));
            for(j = 0; j< tmp_mutex_info->hold_lk_cnt ; j++)
            {
                CTC_SAL_DEBUG_OUT("Hold Lock NO.%-2d Location : %-64s\n", j, tmp_mutex_info->hold_lk[j]->lk_hold_location);
            }
            if(!all)
            {
                break;
            }
        }
    }
    if(mutex_mutex)
    {
        /*for multi task*/
        up(&mutex_mutex->sem);
    }
    CTC_SAL_DEBUG_OUT("===========================================================================\n");
}

sal_err_t
ctc_sal_mutex_init(void)
{
    int ret = 0;

    if(!g_task_mutex.init)
    {
        sal_memset(&g_task_mutex,0,sizeof(sal_task_mutex_t));
        ret = ctc_sal_mutex_create(&mutex_mutex);
        g_task_mutex.init = 1;
    }
    return ret;
}

void
ctc_sal_mutex_deinit(void)
{
    uint8 i = 0;

    ctc_sal_mutex_destroy(mutex_mutex);

    for(i = 0; i < g_task_mutex.tid_num; i++)
    {
        SAL_FREE(g_task_mutex.info[i]);
        break;
    }
    sal_memset(&g_task_mutex,0,sizeof(sal_task_mutex_t));
}

#else

void
ctc_sal_mutex_lock(sal_mutex_t* mutex)
{
    int ret = 0;

    if (!mutex)
    {
        return ;
    }

    ret = down_interruptible(&mutex->sem);

    return;
}

void
ctc_sal_mutex_unlock(sal_mutex_t* mutex)
{
    if (!mutex)
    {
        return ;
    }

    up(&mutex->sem);
}
#endif
bool
ctc_sal_mutex_try_lock(sal_mutex_t* mutex)
{
    if (!mutex)
    {
        return FALSE;
    }

    return down_trylock(&mutex->sem) == 0;
}

sal_err_t
ctc_sal_sem_create(sal_sem_t** pp_sem, uint32 init_count)
{
    sal_sem_t* sem;

    SAL_MALLOC(sem, sal_sem_t*, sizeof(sal_sem_t));
    if (!sem)
    {
        return ENOMEM;
    }

    sema_init(&sem->sem, 0);

    *pp_sem = sem;
    return 0;
}

sal_err_t
ctc_sal_sem_destroy(sal_sem_t* p_sem)
{
    if (!p_sem)
    {
        return EINVAL;
    }

    SAL_FREE(p_sem);
    return 0;
}

sal_err_t
ctc_sal_sem_take(sal_sem_t* p_sem, int32 msec)
{
    int ret;

    if (!p_sem)
    {
        CTC_SAL_DEBUG_OUT("Sem is NULL!!!!\n");
        return EINVAL;
    }

    if (msec == SAL_SEM_FOREVER) {
        ret = down_interruptible(&p_sem->sem);
	} else {
		long jiffies = msecs_to_jiffies(msec);

        ret = down_timeout(&p_sem->sem, jiffies);
    }

    return ret;
}

sal_err_t
ctc_sal_sem_give(sal_sem_t* p_sem)
{
    if (!p_sem)
    {
        return EINVAL;
    }

    up(&p_sem->sem);
    return 0;
}

static const uint16 logtable[256] = {
    0x0000, 0x0171, 0x02e0, 0x044e, 0x05ba, 0x0725, 0x088e, 0x09f7,
    0x0b5d, 0x0cc3, 0x0e27, 0x0f8a, 0x10eb, 0x124b, 0x13aa, 0x1508,
    0x1664, 0x17bf, 0x1919, 0x1a71, 0x1bc8, 0x1d1e, 0x1e73, 0x1fc6,
    0x2119, 0x226a, 0x23ba, 0x2508, 0x2656, 0x27a2, 0x28ed, 0x2a37,
    0x2b80, 0x2cc8, 0x2e0f, 0x2f54, 0x3098, 0x31dc, 0x331e, 0x345f,
    0x359f, 0x36de, 0x381b, 0x3958, 0x3a94, 0x3bce, 0x3d08, 0x3e41,
    0x3f78, 0x40af, 0x41e4, 0x4319, 0x444c, 0x457f, 0x46b0, 0x47e1,
    0x4910, 0x4a3f, 0x4b6c, 0x4c99, 0x4dc5, 0x4eef, 0x5019, 0x5142,
    0x526a, 0x5391, 0x54b7, 0x55dc, 0x5700, 0x5824, 0x5946, 0x5a68,
    0x5b89, 0x5ca8, 0x5dc7, 0x5ee5, 0x6003, 0x611f, 0x623a, 0x6355,
    0x646f, 0x6588, 0x66a0, 0x67b7, 0x68ce, 0x69e4, 0x6af8, 0x6c0c,
    0x6d20, 0x6e32, 0x6f44, 0x7055, 0x7165, 0x7274, 0x7383, 0x7490,
    0x759d, 0x76aa, 0x77b5, 0x78c0, 0x79ca, 0x7ad3, 0x7bdb, 0x7ce3,
    0x7dea, 0x7ef0, 0x7ff6, 0x80fb, 0x81ff, 0x8302, 0x8405, 0x8507,
    0x8608, 0x8709, 0x8809, 0x8908, 0x8a06, 0x8b04, 0x8c01, 0x8cfe,
    0x8dfa, 0x8ef5, 0x8fef, 0x90e9, 0x91e2, 0x92db, 0x93d2, 0x94ca,
    0x95c0, 0x96b6, 0x97ab, 0x98a0, 0x9994, 0x9a87, 0x9b7a, 0x9c6c,
    0x9d5e, 0x9e4f, 0x9f3f, 0xa02e, 0xa11e, 0xa20c, 0xa2fa, 0xa3e7,
    0xa4d4, 0xa5c0, 0xa6ab, 0xa796, 0xa881, 0xa96a, 0xaa53, 0xab3c,
    0xac24, 0xad0c, 0xadf2, 0xaed9, 0xafbe, 0xb0a4, 0xb188, 0xb26c,
    0xb350, 0xb433, 0xb515, 0xb5f7, 0xb6d9, 0xb7ba, 0xb89a, 0xb97a,
    0xba59, 0xbb38, 0xbc16, 0xbcf4, 0xbdd1, 0xbead, 0xbf8a, 0xc065,
    0xc140, 0xc21b, 0xc2f5, 0xc3cf, 0xc4a8, 0xc580, 0xc658, 0xc730,
    0xc807, 0xc8de, 0xc9b4, 0xca8a, 0xcb5f, 0xcc34, 0xcd08, 0xcddc,
    0xceaf, 0xcf82, 0xd054, 0xd126, 0xd1f7, 0xd2c8, 0xd399, 0xd469,
    0xd538, 0xd607, 0xd6d6, 0xd7a4, 0xd872, 0xd93f, 0xda0c, 0xdad9,
    0xdba5, 0xdc70, 0xdd3b, 0xde06, 0xded0, 0xdf9a, 0xe063, 0xe12c,
    0xe1f5, 0xe2bd, 0xe385, 0xe44c, 0xe513, 0xe5d9, 0xe69f, 0xe765,
    0xe82a, 0xe8ef, 0xe9b3, 0xea77, 0xeb3b, 0xebfe, 0xecc1, 0xed83,
    0xee45, 0xef06, 0xefc8, 0xf088, 0xf149, 0xf209, 0xf2c8, 0xf387,
    0xf446, 0xf505, 0xf5c3, 0xf680, 0xf73e, 0xf7fb, 0xf8b7, 0xf973,
    0xfa2f, 0xfaea, 0xfba5, 0xfc60, 0xfd1a, 0xfdd4, 0xfe8e, 0xff47
};

/**
 * ctc_sal_intlog2 - computes the log2 value
 * @value: the number to be calculated
 *
 * Computes: log2(value) * 2^24
 */
uint64
ctc_sal_intlog2(uint32 value)
{
    uint32 msb;
    uint32 logentry;
    uint32 significand;
    uint32 interpolation;

    if (value == 0) {
        return 0;
    }

    /* first detect the msb (count begins at 0) */
    msb = fls(value) - 1;

    /**
     *    now we use a logtable after the following method:
     *
     *    log2(2^x * y) * 2^24 = x * 2^24 + log2(y) * 2^24
     *    where x = msb and therefore 1 <= y < 2
     *    first y is determined by shifting the value left
     *    so that msb is bit 31
     *        0x00231f56 -> 0x8C7D5800
     *    the result is y * 2^31 -> "significand"
     *    then the highest 9 bits are used for a table lookup
     *    the highest bit is discarded because it's always set
     *    the highest nine bits in our example are 100011000
     *    so we would use the entry 0x18
     */
    significand = value << (31 - msb);
    logentry = (significand >> 23) & 0xff;

    /**
     *    last step we do is interpolation because of the
     *    limitations of the log table the error is that part of
     *    the significand which isn't used for lookup then we
     *    compute the ratio between the error and the next table entry
     *    and interpolate it between the log table entry used and the
     *    next one the biggest error possible is 0x7fffff
     *    (in our example it's 0x7D5800)
     *    needed value for next table entry is 0x800000
     *    so the interpolation is
     *    (error / 0x800000) * (logtable_next - logtable_current)
     *    in the implementation the division is moved to the end for
     *    better accuracy there is also an overflow correction if
     *    logtable_next is 256
     */
    interpolation = ((significand & 0x7fffff) *
            ((logtable[(logentry + 1) & 0xff] -
              logtable[logentry]) & 0xffff)) >> 15;

    /* now we return the result */
    return ((msb << 24) + (logtable[logentry] << 8) + interpolation);
}

/**
 * ctc_sal_intlog10 - computes the log10 value
 * @value: the number to be calculated
 *
 * Computes: log10(value) * 2^24
 */
uint64
ctc_sal_intlog10(uint32 value)
{ 
    uint64 log;
    if (value == 0) {
        return 0;
    }

    log = ctc_sal_intlog2(value);

    /**
    we use the following method:
    log10(x) = log2(x) * log10(2)
    */
    return (log * 646456993) >> 31;
}

/**
 * ctc_sal_intpow - computes the exponentiation of the given base and exponent
 * @base: base which will be raised to the given power
 * @exp: power to be raised to
 *
 * Computes: pow(base, exp), i.e. @base raised to the @exp power
 */
uint64 
ctc_sal_intpow(uint64 base, uint32 exp)
{
    uint64 result = 1;

    while (exp) {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        base *= base;
    }

    return result;
}

/**
 * ctc_sal_intround - cumpute the integer round of base
 * @base: the value to be rounded
 * @factor: the factor of round value
 *
 * Computes: floor(round(base*factor))
 */
uint32 
ctc_sal_intround(uint32 base, uint32 factor)
{
    base += factor;
    base -= base % (factor*2);
    return base;
}

/**
 * ctc_sal_intsqrt - computes the integer square root
 * @x: integer of which to calculate the sqrt
 *
 * Computes: floor(sqrt(x))
 */
uint64 
ctc_sal_intsqrt(uint64 x)
{
    uint64 b, m, y = 0;

    if (x <= 1)
        return x;

    m = 1UL << ((x) & ~1UL);
    while (m != 0) {
        b = y + m;
        y >>= 1;

        if (x >= b) {
            x -= b;
            y += m;
        }
        m >>= 2;
    }

    return y;
}

